﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Threading;
using System.IO;
using System.Drawing.Imaging;
using Emgu.CV;
using Emgu.CV.Structure;

namespace GeoPlugin
{
    public class ColorBrain
    {
        public event EventHandler NewResult;
        private bool changed;
        private Point? center;

        public Point? Center
        {
            get { return center; }
            set { center = value; }
        }

        public bool Changed
        {
            get { return changed; }
            set { changed = value; }
        }

        private List<Spat> spats;
        private Bitmap image;

        public Bitmap Image
        {
            get
            {
                return image;
            }
            set
            {
                image = value;
            }
        }
        private Bitmap markerImage;
        private static bool firstTime = true;

        public Bitmap MarkerImage
        {
            get { return markerImage; }
            set { markerImage = value; }
        }

        public ColorBrain(List<Spat> spats)
        {
            this.changed = false;
            this.spats = spats;
            this.MarkerImage = ImageProcessing.NewEmptyImage(320, 240, Color.Black);
            this.Image = new Bitmap(320, 240, PixelFormat.Format24bppRgb);
        }


        private void searchFirstPoint(Bitmap image, Spat spat)
        {
            int i, j;
            int startPositionX, startPositionY;
            int leftSearchX, rightSearchX, topSearchY, bottomSearchY, startX, stopX, startY, stopY;
            int marginX = image.Width;
            int marginY = image.Height;

            // If firstPoint was not previously found, start search from the middle of screen
            if (spat.FirstPoint == null)
            {
                startPositionX = marginX / 2;
                startPositionY = marginY / 2;
                Console.WriteLine("New firstpoint");
            }
            // If firstPoint was previously found, start search from there (assume not much 
            // movement between 2 frames)
            else
            {
                startPositionX = spat.FirstPoint.Value.X;
                startPositionY = spat.FirstPoint.Value.Y;
                spat.FirstPoint = null;
            }

            //Search in "ripple mode": closest neighbours first, then the ones surrounding them,
            // and so on.. (modified to go + 2 in increments <skip one ripple>)
            for (j = 0; j < Math.Max(marginX, marginY); j = j + 2)
            {
                startX = leftSearchX = startPositionX - j;
                stopX = rightSearchX = startPositionX + j;
                startY = topSearchY = startPositionY - j;
                stopY = bottomSearchY = startPositionY + j;

                //Start with the upper pixel(s)
                if (topSearchY >= 0)
                {
                    if (leftSearchX < 0)
                    {
                        startX = 0;
                    }
                    if (rightSearchX >= marginX)
                    {
                        stopX = marginX - 1;
                    }
                    //search every second pixel (faster)
                    for (i = startX; i <= stopX; i = i + 2)
                    {
                        if (pixelBelongsToSpot(image.GetPixel(i, topSearchY), spat))
                        {
                            spat.FirstPoint = new Point(i, topSearchY);
                            return;
                        }
                    }
                }

                //Continue with the left pixel(s)
                if (leftSearchX >= 0)
                {
                    if (topSearchY < 0)
                    {
                        startY = 0;
                    }
                    if (bottomSearchY >= marginY)
                    {
                        stopY = marginY - 1;
                    }
                    for (i = startY + 1; i <= stopY - 1; i = i + 2)
                    {
                        if (pixelBelongsToSpot(image.GetPixel(leftSearchX, i), spat))
                        {
                            spat.FirstPoint = new Point(leftSearchX, i);
                            return;
                        }
                    }
                }

                //Continue with the bottom pixel(s)
                if (bottomSearchY < marginY)
                {
                    if (leftSearchX < 0)
                    {
                        startX = 0;
                    }
                    if (rightSearchX >= marginX)
                    {
                        stopX = marginX - 1;
                    }
                    for (i = startX; i <= stopX; i = i + 2)
                    {
                        if (pixelBelongsToSpot(image.GetPixel(i, bottomSearchY), spat))
                        {
                            spat.FirstPoint = new Point(i, bottomSearchY);
                            return;
                        }
                    }
                }

                //Continue with the right pixel(s)
                if (rightSearchX < marginX)
                {
                    if (topSearchY < 0)
                    {
                        startY = 0;
                    }
                    if (bottomSearchY >= marginY)
                    {
                        stopY = marginY - 1;
                    }
                    for (i = startY + 1; i <= stopY - 1; i = i + 2)
                    {
                        if (pixelBelongsToSpot(image.GetPixel(rightSearchX, i), spat))
                        {
                            spat.FirstPoint = new Point(rightSearchX, i);
                            return;
                        }
                    }
                }
            }
        }

        private void extendSpat(Bitmap image, Spat spat)
        {
            int[] neighx = { -1, 0, 1, -1, 1, -1, 0, 1 };
            int[] neighy = { -1, -1, 1, 0, 0, 1, 1, -1 };
            Queue<Point?> queue = new Queue<Point?>();
            Point? newPoint, possiblePoint;
            int possiblePointX, possiblePointY;
            int i;

            int marginX = image.Width;
            int marginY = image.Height;

            if (!(spat.FirstPoint == null))
            {
                spat.reset();
                queue.Enqueue(spat.FirstPoint);
                while (queue.Count > 0)
                {
                    newPoint = queue.Dequeue();
                    spat.myAdd(newPoint);
                    if (spat.Count > image.Width / 4)
                    {
                        queue.Clear();
                        return;
                    }
                    for (i = 0; i < 8; i++)
                    {

                        possiblePointX = newPoint.Value.X + neighx[i];
                        possiblePointY = newPoint.Value.Y + neighy[i];
                        possiblePoint = new Point(possiblePointX, possiblePointY);

                        if ((possiblePointX >= 0) && (possiblePointX < marginX)
                            && (possiblePointY >= 0) && (possiblePointY < marginY)
                                && (!spat.Contains(possiblePoint)) && (!queue.Contains(possiblePoint)))
                        {


                            if (pixelBelongsToSpot(image.GetPixel(possiblePointX, possiblePointY), spat))
                            {
                                queue.Enqueue(possiblePoint);
                            }

                        }
                    }
                }
            }
        }

        private static bool pixelBelongsToSpot(Color pixelColor, Spat spat)
        {
            if ((spat.Tolerance > Math.Abs(pixelColor.R - spat.Color.R))
                && (spat.Tolerance > Math.Abs(pixelColor.G - spat.Color.G))
                && (spat.Tolerance > Math.Abs(pixelColor.B - spat.Color.B)))
            {
                return true;
            }
            return false;
        }


        private void updateMarkerImage()
        {
            this.markerImage = ImageProcessing.NewEmptyImage(this.Image.Width, this.Image.Height, Color.Black);

            spats.ForEach(spat =>
            {
                if (spat.FirstPoint != null)
                {
                    Console.WriteLine("Found spot..updating markerImage.");
                    spat.ForEach(point =>
                    {
                        if (point != null)
                        {
                            this.markerImage.SetPixel(point.Value.X, point.Value.Y, spat.Color);
                        }
                    });
                    this.markerImage.SetPixel(spat.FirstPoint.Value.X, spat.FirstPoint.Value.Y, Color.White);
                    if ((spat.FirstPoint.Value.X > 4)
                            && (spat.FirstPoint.Value.Y > 4)
                            && (spat.FirstPoint.Value.X < (this.image.Width - 4))
                            && (spat.FirstPoint.Value.Y < (this.image.Height - 4)))
                        for (int i = 1; i < 4; i++)
                        {
                            this.markerImage.SetPixel(spat.FirstPoint.Value.X - i, spat.FirstPoint.Value.Y - i, Color.White);
                            this.markerImage.SetPixel(spat.FirstPoint.Value.X - i, spat.FirstPoint.Value.Y + i, Color.White);
                            this.markerImage.SetPixel(spat.FirstPoint.Value.X + i, spat.FirstPoint.Value.Y - i, Color.White);
                            this.markerImage.SetPixel(spat.FirstPoint.Value.X + i, spat.FirstPoint.Value.Y + i, Color.White);
                        }
                    Point? center = spat.getCenter();
                    if ((center.Value.X > 4)
                        && (center.Value.Y > 4)
                        && (center.Value.X < (this.Image.Width - 4))
                        && (center.Value.Y < (this.Image.Height - 4)))
                    {
                        this.markerImage.SetPixel(center.Value.X, center.Value.Y, Color.Red);
                        for (int i = 1; i < 4; i++)
                        {
                            this.markerImage.SetPixel(center.Value.X - i, center.Value.Y - i, Color.Red);
                            this.markerImage.SetPixel(center.Value.X - i, center.Value.Y + i, Color.Red);
                            this.markerImage.SetPixel(center.Value.X + i, center.Value.Y - i, Color.Red);
                            this.markerImage.SetPixel(center.Value.X + i, center.Value.Y + i, Color.Red);
                        }
                    }
                    this.Center = center;
                }
                else
                    this.Center = null;
            });
        }


        public void HandleEvent(object sender, EventArgs args)
        {
            if (this.changed == false)
            {
                this.Image = ImageProcessing.CopyBitmap(PluginModel.Instance.Image);
            }
            if (firstTime && this.Image != null)
            {
                this.markerImage = ImageProcessing.NewEmptyImage(this.Image.Width, this.Image.Height, Color.Black);
                firstTime = false;
            }
            this.changed = true;
        }

        public void Run()
        {
            while (true)
            {
                if (this.changed && !firstTime && this.Image != null)
                {
                    Image<Rgb, Byte> currentFrame = new Image<Rgb,byte>(this.Image);
                    Image<Gray, Byte>[] channels = currentFrame.Split();
                    channels[0]._ThresholdBinary(new Gray(220),new Gray(255));
                    channels[1]._ThresholdBinary(new Gray(130),new Gray(190));
                    channels[2]._ThresholdBinary(new Gray(0),new Gray(40));
                    channels[0].And(channels[1]);
                    channels[0].And(channels[2]);
                    channels[0].Erode(1);
                    channels[0].Dilate(2);
                    this.markerImage = channels[0].Bitmap;
                    NotifyComponents();
                    this.changed = false;
                }

                Thread.Sleep(500);
            }
        }

        public void NotifyComponents()
        {
            EventHandler handler = NewResult;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }
}
